﻿/*
 * @Autor: PK
 * @Date: 2020-12-24 10:08:47
 * @Description: Agency 排队中的服务机构，父类，如要实现如站台、道路等服务员身份的类，请继承它，内部服务台请继承InternalAgency
 * 如没有特殊必要请不要重写Arrive()、Serve()、Depart()，请考虑对 Wait*List 属性的影响
 */

using System;
using System.Collections.Generic;
using NLog;
using O2DESNet;
using ShkSimulation.core.dao.statistic;
using ShkSimulation.core.exceptions;
using ShkSimulation.core.template.customer;
using ShkSimulation.core.template.model;
using ShkSimulation.core.util;

namespace ShkSimulation.core.template.agency {

    public abstract class Agency: Sandbox, NetServer, InitAndDispose, ServeOrder, DataReporter{
        
        protected static readonly Logger Logger = LogManager.GetLogger(nameof(Agency));

        public int UniqueId { get; set; }
        public string Uniquekey => UniqueId.ToString();
        public int Order {get; set;}
        public int ServerCount { set; get; }
        public int QueueIndex { get; set; }
        public string Name { get; set; }
        public string Identify { get; set; }
        public Dictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>();
        
        bool internalAgenciesSet;
        protected InternalAgencyChain InternalAgencyChain { get; private set; }
        public abstract ServiceRateHelper ServiceRateHelper { get; set; } 

        protected Agency(BaseAgencyParams agencyParams) 
            : base(agencyParams.Seed, agencyParams.UniqueId.ToString()) {
            this.UniqueId = agencyParams.UniqueId;
            this.ServerCount = agencyParams.ServerCount;
        }
        
        public object this[string name] {
            get { 
                if (Attributes.ContainsKey(name)){
                    return Attributes[name];
                }
                return null;
            }
            set {
                Attributes[name] = value;
            }
        }

        #region 接口默认实现
        
        /**
        * @description: DataReport
        */
        public event Action<DataReporter> ReportToCollection = _ => { };

        public abstract DataReportInfo GetReportInfo();

        public void SetInternalAgencies(List<InternalAgency> agencies){
            if (internalAgenciesSet) {
                throw new ErrorParamException("不可以重复设置内部服务台");
            }
            InternalAgencyChain = new InternalAgencyChain(this, agencies);
            InternalAgencyChain.Agency = this;
            foreach (InternalAgency each in agencies) {
                AddChild(each);
                each.ParentAgency = this;
                each.Chain = InternalAgencyChain;
            }
            internalAgenciesSet = true;
        }

        public void SetInternalAgency(InternalAgency agency) {
            SetInternalAgencies(new List<InternalAgency> { agency });
        }

        /**
         * @description: 默认使用反射对相应属性或字段赋值，如有其他需要请重写，因此最好保证该方法只在初始化时使用，
         * @param 需要通过反射设置的public属性或字段，非常不建议将字段设置为public
         */
        public void SetPropertyAndField(Dictionary<string, object> attributes){
            RelectionUtil.SetPropertyAndField(this, attributes);
        }

        #endregion

        #region Schedule
        /*
         * ScheduleXXX系列方法总体的思路都是将Customer放到Agency内部服务台的容器中，然后调用XXX()方法，该方法则会调用用户实现的XXX(Customer/Travler...)等方法
         * @Description: 这个方法与ScheduleServe、ScheduleDepart有所不同，是预定Customer的EnterNext方法来进行预约服务，顾客也米有放在服务台中，因此Customer的EnterNext一定要符合自己的仿真流程
         */
        public void ScheduleArrive(Customer customer, DateTime clockTime, string tag = null){
            Schedule(() => InternalArrive(customer), clockTime, tag);
        }
        public void ScheduleArrive(Customer customer, TimeSpan delay, string tag = null){
            ScheduleArrive(customer, ClockTime + delay, tag);
        }
        public void ScheduleServe(Customer customer, DateTime clockTime, string tag = null) {
            Schedule(() => InternalServe(customer), clockTime, tag);
        }
        public void ScheduleServe(Customer customer, TimeSpan delay, string tag = null){
            ScheduleServe(customer, ClockTime + delay, tag);
        }
        public void ScheduleDepart(Customer customer, DateTime clockTime, string tag = null){
            Schedule(() => InternalDepart(customer), clockTime, tag);
        }
        public void ScheduleDepart(Customer customer, TimeSpan delay, string tag = null){
            ScheduleDepart(customer, ClockTime + delay, tag);
        }
        #endregion

        public void InternalArrive(Customer customer) {
            InternalAgencyChain chain = GetInternalAgencyChain(customer);
            //注意时间顺序，不要已经离开父服务台了，还有次级服务台在服务
            if (chain != null && chain.BeforeArriveAgencies.Count > 0) {
                customer.BeforeArriveInternalAgencyIndex = 0;
                chain.ChainArrive(customer);
                //子级完成后，父级的服务在AgencyChain中表示
            } else {
                Arrive(customer);
            }
        }
        
        public void InternalDepart(Customer customer) {
            InternalAgencyChain chain = GetInternalAgencyChain(customer);
            //注意时间顺序，不要已经离开父服务台了，还有次级服务台在服务
            if (chain != null && chain.BeforeDepartAgencies.Count > 0) {
                customer.BeforeDepartInternalAgencyIndex = 0;
                chain.ChainDepart(customer);
            } else {
                Depart(customer);
            }
        }

        public void InternalServe(Customer customer) {
            InternalAgencyChain chain = GetInternalAgencyChain(customer);
            //注意时间顺序，不要已经离开父服务台了，还有次级服务台在服务
            if (chain != null && chain.BeforeServeAgencies.Count > 0) {
                customer.BeforeServeInternalAgencyIndex = 0;
                chain.ChainServe(customer);
                //子级完成后，父级的服务在AgencyChain中表示
            } else {
                Serve(customer);
            }
        }
        
        /**
         * @description: 该方法获取该服务台内部的次级服务机构链，在服务台的Serve阶段会进行次级服务台的所有服务
         * @return 内部服务台
         */
        protected virtual InternalAgencyChain GetInternalAgencyChain(Customer customer){
            return InternalAgencyChain;
        }
        
        public abstract void Arrive(Customer customer);
        public abstract void Serve(Customer customer);
        public abstract void Depart(Customer customer);

    }
}
